Fatigue Evaluation - Evolution of Median Power Frequency
Difficulty Level:
Tags pre-process☁emg

In contrast to the cardiac muscle, skeletal muscles are very susceptible to fatigue when exposed to an intense activity, for example, during a sports practice.

Fatigue can be defined as a complex physiological phenomenon with several causes and dependent of different mechanisms (accordingly to Cifrek et. al. ).

Fatigue may have serious consequences, being one triggering phenomenon behind muscular injuries.

So, taking into account the relevance of this phenomenon, fatigue study and monitoring is a promising research area, with some decades. During this period, it was demonstrated that some parameters extracted from EMG signal evolve in a particular way as fatigue is being acquired.

One of this parameters, and probably the most consensual one, is the median power frequency , that decreases along fatigue acquisition, i.e. the power spectrum suffers a compression to lowest frequency components .

In this Jupyter Notebook it will be presented the basic methodology to monitoring the fatigue along time.


1 - Importation of the needed packages

In [1]:
# OpenSignals Tools own package for loading and plotting the acquired data
import biosignalsnotebooks as bsnb
import biosignalsnotebooks.signal_samples as bsnb_ss

# Scientific packages
from numpy import linspace, where
from scipy.signal import periodogram
from scipy.integrate import cumtrapz
In [2]:
# Base packages used in OpenSignals Tools Notebooks for ploting data
from bokeh.plotting import output_file, show
from bokeh.io import output_notebook
from bokeh.layouts import gridplot
from bokeh.models import BoxAnnotation
output_notebook(hide_banner=True)

2 - Load of acquired EMG data, collected during a fatigue induction trial ( biceps brachii isometric contraction)

In [3]:
# Data loading
data, header = bsnb_ss.load_signal("emg_fatigue", get_header=True)

3 - Mac address identification for the device and the channel used during acquisition

In [4]:
mac_address = list(header.keys())[0]
channel = "CH" + str(header[mac_address]["channels"][0])

print ("Mac Address: " + str(mac_address) + " Channel: " + channel)
Mac Address: 00:07:80:4C:01:B1 Channel: CH2

4 - Sampling rate and acquired data samples storage (internal variables)

In [5]:
# Sampling rate and acquired data
sr = header[mac_address]["sampling rate"]

# Signal Samples
signal = data[mac_address][channel]
time = linspace(0, len(signal) / sr, len(signal))

5 - Muscular activations detection

Each muscular activation defines a processing window

In [6]:
# The default call of detect_emg_activations function is:
# detect_emg_activations(emg_signal, sample_rate, smooth_level=20, threshold_level=10, time_units=False, volts=False,
# resolution=None, device="biosignalsplux", plot_result=False)
# This function returns the samples where each activation period starts and ends (first two outputs, reason why it is specified
# [:2]) the smoothed EMG signal samples and threshold level. Samples above the threshold are converted to 1 and the samples
# below to 0, which give rise to a rectangular activation signal.
activation_begin, activation_end = bsnb.detect_emg_activations(signal, sr)[:2]

bsnb.detect_emg_activations previous call has explicit and implicit arguments. The list containing the signal samples collected during the acquisition and the sampling rate at which the acquisition was carried out are the explicit ones, while smooth_level and threshold_level correspond to implicit arguments that assume predefined values of 20 % and 10 %, respectively.

Changing these two parameters will produce changes in the detection of muscular activations, as can be seen at the following figure.

In [7]:
bsnb.plot_compare_act_config(signal, sr)

6 - Extraction of the Median Power Frequency that characterises each muscular activation (processing window)

The Median Power Frequency is defined as the frequency value that allows the power spectrum division into two regions with equal power

\begin{equation} \int_0^{f_{median}} PSD(f) df = \frac{1}{2} \int_0^{sr/2} PSD(f) df \end{equation} where $f_{median}$ defines the median power frequency, $PSD(f)$ is the power spectral density estimate, after decomposing the signal by applying the Fourier Transform, for the elementary component with frequency f. The term $sr$ refers to the "sampling rate" abbreviation.

In [8]:
# Iteration along muscular activations
median_freq_data = []
median_freq_time = []
for activation in range(0, len(activation_begin)):
    processing_window = signal[activation_begin[activation]:activation_end[activation]]
    central_point = (activation_begin[activation] + activation_end[activation]) / 2
    median_freq_time += [central_point / sr]

    # Processing window power spectrum (PSD) generation
    freqs, power = periodogram(processing_window, fs=sr)

    # Median power frequency determination
    area_freq = cumtrapz(power, freqs, initial=0)
    total_power = area_freq[-1]
    median_freq_data += [freqs[where(area_freq >= total_power / 2)[0][0]]]
    # The previous indexation [0][0] was specified in order to only the first sample that verifies
    # the condition area_freq >= total_power / 2 be returned (all the subsequent samples will verify
    # this condition, but, we only want the frequency that is nearest to the ideal frequency value
    # that divides power spectrum into to regions with the same power - which is not achievable in
    # a digital processing perspective).

7 - Graphical Representation of the Median Frequency evolution time series

In [9]:
bsnb.plot_median_freq_evol(time, signal, median_freq_time, median_freq_data, activation_begin, activation_end, sr)

Conclusion

For the present acquisition the results demonstrate that fatigue settles in during the experimental trial, meeting the behavior reported in EMG studies reviewed by Cifrek et. al. and well developed by different authors such as Merletti or De Luca , i.e., the decrease of median power frequency.

This procedure can be automatically done by fatigue_eval_med_freq function in extract module of biosignalsnotebooks package. The generated output is a pandas DataFrame

In [10]:
bsnb.fatigue_eval_med_freq(signal, sr)
Out[10]:
Time (s) Median Frequency (Hz)
0 2.7920 78.12500
1 7.0210 70.31250
2 11.2805 78.12500
3 15.1895 70.31250
4 19.3120 74.21875
5 23.1825 78.12500
6 27.1540 74.21875
7 31.3080 74.21875
8 35.2350 74.21875
9 39.1450 82.03125
10 42.8330 66.40625
11 47.0160 70.31250
12 50.9740 66.40625
13 54.9625 66.40625
14 59.1560 70.31250
15 62.9980 70.31250
16 67.3105 62.50000
17 71.2715 70.31250
18 75.2385 66.40625
19 79.1415 66.40625
20 82.9460 62.50000
21 86.8410 66.40625
22 90.8875 62.50000
23 95.0355 62.50000
24 98.9180 66.40625
25 103.0290 58.59375
26 107.2275 62.50000
27 110.9635 62.50000
28 115.1860 54.68750
29 119.4930 54.68750
In [11]:
from biosignalsnotebooks.__notebook_support__ import css_style_apply
css_style_apply()
.................... CSS Style Applied to Jupyter Notebook .........................
Out[11]: